﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Dragoon
{
    /// <summary>
    /// Draw 3d vector geometry elements
    /// </summary>
    abstract class VectorGeometry<vertexT> : EffectComponent
        where vertexT : struct
    {
        private short[] _indices;
        private vertexT[] _vertices;
        private VertexDeclaration _vertexDeclaration;

        private List<short> _buildIndexes;
        private List<vertexT> _buildVertices;

        protected VectorGeometry(Game g)
            : base(g)
        {
        }

        protected abstract VertexElement[] VertexElements();
        protected abstract int VertexSize();

        protected void BuildStart()
        {
            _buildVertices = new List<vertexT>();
            _buildIndexes = new List<short>();
        }

        protected void BuildEnd()
        {
            int vertexBufferSize = VertexSize() * _buildVertices.Count;
            _indices = _buildIndexes.ToArray();
            _vertices = _buildVertices.ToArray();
            _vertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexElements());
            _buildIndexes = null;
            _buildVertices = null;
        }

        public override void Draw(GameTime gameTime)
        {
            GraphicsDevice.VertexDeclaration = _vertexDeclaration;
            _effect.Begin();
            foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                GraphicsDevice.DrawUserIndexedPrimitives(                    
                        PrimitiveType.TriangleList,
                        _vertices,
                        0,
                        _vertices.Length,
                        _indices,
                        0,                    // first index element to read
                        _indices.Length / 3   // number of primitives (i.e triangles) to draw
                    );
                pass.End();
            }
            _effect.End();
            base.Draw(gameTime);
        }

        abstract protected vertexT NewVertex(Vector3 position);

        private short BuildIndexOf(vertexT p)
        {
            int index = _buildVertices.IndexOf(p);
            if (index > -1)
                return (short)index;

            if (_buildVertices.Count == short.MaxValue)
                throw new Exception("Too many points in Geometry");

            _buildVertices.Add(p);
            return (short)(_buildVertices.Count - 1);
        }

        private short BuildIndexOf(Vector3 v)
        {
            return BuildIndexOf(NewVertex(v));
        }

        virtual public void BuildTriangle(Vector3 p1, Vector3 p2, Vector3 p3)
        {
            Vector3 r1 = p2 - p1;
            Vector3 r2 = p3 - p2;
            _buildIndexes.Add(BuildIndexOf(p3));
            _buildIndexes.Add(BuildIndexOf(p2));
            _buildIndexes.Add(BuildIndexOf(p1));
        }

        /// <summary>
        /// Sequence clockwise on visible plane
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="p3"></param>
        /// <param name="p4"></param>
        public void BuildQuadrangle(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
        {
            BuildTriangle(p1, p2, p3);
            BuildTriangle(p3, p4, p1);
        }

        public void BuildCube(Vector3 center, float size)
        {
            BuildCube(center, size, size, size);
        }



        public void BuildCube(Vector3 center, float width, float length, float thickness)
        {
            Vector3 c1 = center
                         + Vector3.Left * (width / 2.0F)
                         + Vector3.Backward * (length / 2.0F)
                         + Vector3.Up * (thickness / 2.0f);
            Vector3 c2 = c1 + Vector3.Right * width;
            Vector3 c3 = c2 + Vector3.Forward * length;
            Vector3 c4 = c3 + Vector3.Left * width;
            Vector3 c5 = c1 + Vector3.Down * thickness;
            Vector3 c6 = c2 + Vector3.Down * thickness;
            Vector3 c7 = c3 + Vector3.Down * thickness;
            Vector3 c8 = c4 + Vector3.Down * thickness;
            BuildQuadrangle(c1, c2, c3, c4); // top
            BuildQuadrangle(c5, c8, c7, c6); // bottom
            BuildQuadrangle(c1, c4, c8, c5); // left
            BuildQuadrangle(c3, c2, c6, c7); // right            
            BuildQuadrangle(c4, c3, c7, c8); // front
            BuildQuadrangle(c1, c5, c6, c2); // back
            
        }

        public void BuildCube(Vector3 center, Vector3 sideLengths)
        {
            BuildCube(center, sideLengths.X, sideLengths.Z, sideLengths.Y);
        }

        public void BuildLine(Vector3 from, Vector3 to, float radius, int segments)
        {
            Vector3 dir = to - from;
            float distance = dir.Length();
            dir.Normalize();
            float incAngle = -MathHelper.Pi * 2.0f / segments;
            float atAngle = MathHelper.PiOver4; // rotate 45 deg
            Vector3[] fromPoints = new Vector3[segments];
            Vector3[] toPoints = new Vector3[segments];
            Matrix transform;
            Vector3 startPoint = Vector3.Up * radius;
            for (int i = 0; i < segments; i++)
            {
                Vector3 p = new Vector3(radius, 0, 0);
                transform =
                    Matrix.CreateTranslation(startPoint)
                    * Matrix.CreateFromAxisAngle(dir, atAngle)
                    * Matrix.CreateTranslation(from);
                fromPoints[i] = transform.Translation;
                transform =
                    Matrix.CreateTranslation(startPoint)
                    * Matrix.CreateFromAxisAngle(dir, atAngle)
                    * Matrix.CreateTranslation(to);
                toPoints[i] = transform.Translation;
                atAngle += incAngle;
            }
            Vector3 p1, p2, p3, p4;
            for (int i = 0; i < segments; i++)
            {
                p1 = fromPoints[i];
                p2 = toPoints[i];
                if (i + 1 == segments)
                {
                    p3 = toPoints[0];
                    p4 = fromPoints[0];
                }
                else
                {
                    p3 = toPoints[i + 1];
                    p4 = fromPoints[i + 1];
                }
                BuildQuadrangle(p1, p2, p3, p4);
                BuildTriangle(p1, p4, from);
                BuildTriangle(p3, p2, to);
            }

        }


    }

    abstract class VectorGeometryColored : VectorGeometry<VertexPositionNormalColored>
    {
        protected VectorGeometryColored(Game g) : base(g) { }
        protected Color BuildColor = Color.WhiteSmoke;
        protected Vector3 BuildNormal = Vector3.Up;


        protected override VertexPositionNormalColored NewVertex(Vector3 position)
        {
            VertexPositionNormalColored result = new VertexPositionNormalColored();
            result.Color = BuildColor;
            result.Normal = BuildNormal;
            result.Position = position;
            return result;
        }

        public override void BuildTriangle(Vector3 p1, Vector3 p2, Vector3 p3)
        {
            p1.Normal(ref p2, ref p3, ref BuildNormal);
            base.BuildTriangle(p1,p2,p3);
        }

        sealed protected override VertexElement[] VertexElements()
        {
            return VertexPositionNormalColored.VertexElements;
        }

        sealed protected override int VertexSize()
        {
            return VertexPositionNormalColored.SizeInBytes;
        }

        public override void Draw(GameTime gameTime)
        {
            _effect.CurrentTechnique = _effect.Techniques["Colored"];
            base.Draw(gameTime);
        }
    }
}
